using Adoor.Object.Domain;

#region
/* Adoor.NET - Object/Relational mapping framework leveraging ADO.NET`s powerful disconnected mode.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */ 
#endregion

namespace Adoor.Object.Relational
{
	using System;
	using System.Collections.Generic;
	using System.Xml;
	using System.Data;
	using System.Diagnostics;
	using Adoor.Expression;
    using Adoor.Expression.AST;
    using Adoor.Object;
	using Adoor.Object.Entity;
	using Adoor.Relational;
	using Adoor.Data;  // For SmartKey

	public interface IKeyMappingStrategy
	{
		DataColumn[] DataColumns{ get; }
		bool Identity { get; }
		string SortString { get; }
		KeyValue GetKey(DataRow row);
		string PrimaryKey { get; } // Temp: for non-composite keys only

		ExpressionName[] GetKeyColumns(string qualifier);
		RelationalQuery Project(RelationalOperator op, string qualifier);
		ExpressionNode[] GetKeyExpression(string qualifier);
		ExpressionName GetOidExpression(string qualifier);
		Adoor.Expression.AST.SelectExpression[] GetKeySelectExpression(string qualifier);
		
		string GetOid(KeyValue key);
		KeyValue ExtractKey(string oid);

		/* initialization */
		void InsertKeyColumns(IList<DataColumn> columns);
	}

	public abstract class OidData<TEntity>: IOidData, IKeyMappingStrategy
	{
		private EntityData entityData;
		
		protected OidData(EntityData entityData, XmlNode metadata)
		{
			this.entityData = entityData;
			
            XmlNode attr = metadata.Attributes["primaryKey"];
			if (attr == null)
				throw new Exception("attribute @primaryKey not found for type " + this.EntityData.EntityType.Name);
			string primaryKey = attr.Value;
			string[] columns = primaryKey.Split(',');
			this.dataColumns = new DataColumn[columns.Length];
			for (int i = 0; i < columns.Length; i++)
				this.dataColumns[i] = new DataColumn(columns[i].Trim(), typeof(System.Int32));   
			
            this.identity = metadata.SelectSingleNode("@identity[.='true']") != null;
		}

		public EntityData EntityData
		{
			get { return this.entityData; }
		}

		public IKeyMappingStrategy MappingStrategy
		{
			get { return this; }
		}

		public object GetService(Type type)
		{
			return type.IsInstanceOfType(this) ? this : null;
		}

		public abstract string GetOid(KeyValue key);
		public abstract KeyValue ExtractKey(string oid);

//		public abstract EntityData GetActualEntityData2(string oid);

		// IKeyMappingStrategy

		private DataColumn[] dataColumns;
		public DataColumn[] DataColumns
		{
			get { return this.dataColumns; }
		}

		private bool identity;
		public bool Identity
		{
			get { return this.identity; }
		}

		public string SortString
		{
			get
			{
				System.Text.StringBuilder result = new System.Text.StringBuilder();
				foreach(DataColumn dc in this.dataColumns)
				{
					if (result.Length != 0)
						result.Append(", ");
					result.Append(dc.ColumnName);
				}
				return result.ToString();
			}
		}

		public KeyValue GetKey(DataRow row)
		{
			object[] result = new object[this.dataColumns.Length];
			for (int i = 0; i < this.dataColumns.Length; i++)
				result[i] = row[this.dataColumns[i].ColumnName];
			return new KeyValue(result);
		}

		public string PrimaryKey
		{
			get
			{
				if (this.dataColumns.Length != 1)
					throw new NotSupportedException("KeyFactory.PrimaryKey not supported for composite keys");
				return this.dataColumns[0].ColumnName; 
			}
		}

		public ExpressionNode[] GetKeyExpression(string correlation)
		{
			ExpressionName[] names = GetKeyColumns(correlation);
			ExpressionNode[] result = new ExpressionNode[names.Length];
			for (int i = 0; i < result.Length; i++)
				result[i] = names[i];
			return result;
		}

		public virtual Adoor.Expression.AST.SelectExpression[] GetKeySelectExpression(string correlation)
		{
			ExpressionName[] names = GetKeyColumns(correlation);
			SelectExpression[] result = new SelectExpression[names.Length];
			for (int i = 0; i < result.Length; i++)
				result[i] = new SelectExpression( names[i], "");
			return result;
		}

		public RelationalQuery Project(RelationalOperator op, string correlation)
		{
			ExpressionNode[] resultEN = GetKeyExpression(correlation);
			SelectExpression[] result = new SelectExpression[resultEN.Length];
			for (int i = 0; i < result.Length; i++)
				result[i] = new SelectExpression(this.dataColumns[i].ColumnName, (ExpressionName) resultEN[i]);
			return op.Project(result);
		}

		public virtual ExpressionName[] GetKeyColumns(string correlation)
		{
			ExpressionName[] result = new ExpressionName[this.dataColumns.Length];
			for (int i = 0; i < result.Length; i++)
				result[i] = new ExpressionName(correlation, this.dataColumns[i].ColumnName);
			return result;
		}

		public virtual ExpressionName GetOidExpression(string qualifier) // TODO: should not be here
		{
			if (this.DataColumns.Length > 1)
				throw new Exception("OIDs composed of several columns cannot be selected (name = '" + qualifier + "')");
			DataColumn dc = this.DataColumns[0];
			return new ExpressionName(qualifier, dc.ColumnName);
		}

		public void InsertKeyColumns(IList<DataColumn> dataColumns)
		{
			int pos = 0;
			foreach(DataColumn pk in this.dataColumns) 
			{
				DataColumn keyColumn = null;
				foreach(DataColumn dc in dataColumns)
				{
					if (string.Compare(dc.ColumnName, pk.ColumnName, true) == 0)
					{
						keyColumn = dc;
						break;
					}
				}
				if (keyColumn == null) 
				{
					keyColumn = pk;
					dataColumns.Insert(pos++, keyColumn);
				}
				else
					pk.DataType = keyColumn.DataType;
				keyColumn.AllowDBNull = false;
				keyColumn.Unique = true;
			}
		}
	}

    
   
  
	[Obsolete("use CustomKeyFactory instead")]
    public class CompositeKeyFactory<TEntity> : CustomKeyFactory<TEntity>
	{
		public CompositeKeyFactory(EntityData entityData, XmlNode metadata)
			: base(entityData, metadata)
		{
			Debug.Assert(false, "use CustomKeyFactory instead");
		}
	}

  
	

}